import 'dart:convert';

import 'package:test/test.dart';

import '../test_bean_lib/auto_default_value.dart';
import '../test_bean_lib/build_in_json_converter.dart';
import '../test_bean_lib/compat_json_key_feature.dart';
import '../test_bean_lib/custom_json_converter.dart';
import '../test_bean_lib/directly_to_json.dart';
import '../test_bean_lib/enum_feature.dart';
import '../test_bean_lib/field_default_value.dart';
import '../test_bean_lib/gen/base/json_cast.dart';
import '../test_bean_lib/map_without_generic.dart';
import '../test_bean_lib/multiple_fields_with_one_type.dart';
import '../test_bean_lib/repeat_type_name/bean.dart';
import '../test_bean_lib/repeat_type_name/bean2.dart' as bean2;

void errorLog(dynamic e, dynamic stackTrace) {
  throw '$e $stackTrace';
}

///need 'gen_test/ok' build result for features testing
void main() {
  $DefaultJSONConvert.errorLog = errorLog;
  test('compat_json_key_feature', () async {
    final bean = CompatJsonKeyFeature()..a = '1';
    final json = jsonEncode(bean.toJson());
    final map = jsonDecode(json) as Map<String, dynamic>;
    expect(map['a1'], bean.a);
    final bean2 = CompatJsonKeyFeature.fromJson(map);
    expect(jsonEncode(bean2.toJson()), json);
  });

  test('custom_json_converter', () async {
    final bean = CustomJsonConverterBean()
      ..b = true
      ..b2 = [true, false];

    final m = bean.toJson();
    expect(m['b'], 'Y');
    expect(m['b2'], 'Y,N');

    final json = jsonEncode(m);
    final map = jsonDecode(json);
    map['b'] = 1;
    map['b2'] = '1,N';
    final bean2 = CustomJsonConverterBean.fromJson(map);
    expect(jsonEncode(bean2.toJson()), json);
  });

  test('map_without_generic', () async {
    final bean = MapBean()..map = {"a": 1};
    final json = jsonEncode(bean.toJson());
    final map = jsonDecode(json);
    final bean2 = MapBean.fromJson(map);
    expect(bean.map!['a'], bean2.map!['a']);
    expect(jsonEncode(bean2.toJson()), json);
  });

  test('directly_to_json.dart', () async {
    final bean = DirectlyToJsonBean()
      ..a = 1
      ..b = false
      ..d = 1.0
      ..map = {'a': 1}
      ..listInt = [1]
      ..listBool = [false]
      ..listDouble = [1];
    final map = bean.toJson();
    expect(map['a'] == bean.a, true);
    expect(map['b'] == bean.b, true);
    expect(map['d'] == bean.d, true);
    expect(map['map'] == bean.map, true);
    expect(map['listInt'] == bean.listInt, true);
    expect(map['listBool'] == bean.listBool, true);
    expect(map['listDouble'] == bean.listDouble, true);
  });

  test('enum_feature', () async {
    final bean = EnumFeatureBean()
      ..e = Feature.high
      ..e2 = [Feature.high, Feature.low]
      ..e3 = [Feature.high, null];
    final json = jsonEncode(bean.toJson());
    final map = jsonDecode(json);
    final bean2 = EnumFeatureBean.fromJson(map);
    expect(jsonEncode(bean2.toJson()), json);
  });

  test('multiple_fields_with_one_type', () async {
    final bean = MultipleFieldsWithOneType()
      ..a = 'a'
      ..b = 'b'
      ..c = 'c'
      ..d = 'd';
    final json = jsonEncode(bean.toJson());
    final map = jsonDecode(json);
    final bean2 = MultipleFieldsWithOneType.fromJson(map);
    expect(jsonEncode(bean2.toJson()), json);
  });

  test('field_default_value', () async {
    final bean = FieldDefaultValueBean();
    final json = jsonEncode(bean.toJson());
    final map = jsonDecode(json) as Map<String, dynamic>;
    map.clear(); //no 'stringField' value, 'stringField' be convert to default value
    map['e'] = 2; //unknown enum code, 'enumField' should be convert to default value
    final bean2 = FieldDefaultValueBean.fromJson(map);
    expect(jsonEncode(bean2.toJson()), json);
  });

  test('repeat_type_name', () async {
    final a = RepeatTypeNameBean();
    final json = jsonEncode(a.toJson());
    final map = jsonDecode(json);
    final b = bean2.RepeatTypeNameBean.fromJson(map);
    expect(a.s1.name, b.s2.name); //same default enum name
  });

  test('build_in_json_converter', () async {
    final now = DateTime.now();
    final a = BuildInJsonConverterBean()
      ..intA = 1
      ..boolB = false
      ..b2 = [false]
      ..b3 = [false, null]
      ..stringC = '1'
      ..doubleD = 1.0
      ..dateTime = now
      ..dateTime2 = [now]
      ..map = {'a': 1};
    final json = jsonEncode(a.toJson());
    final map = jsonDecode(json);
    map['intA'] = '1';
    map['boolB'] = '0';
    map['b3'] = [0, null];
    map['stringC'] = 1;
    map['doubleD'] = 1;
    map['dateTime'] = now.toIso8601String();
    map['dateTime2'] = [now.toIso8601String()];
    map['map'] = {'a': 1};
    map['map2'] = {'a': '2'};
    final b = BuildInJsonConverterBean.fromJson(map);
    expect(jsonEncode(b.toJson()), json);
  });

  test('global_custom_default_date_converter', () async {
    $JSON.addDefaultFromJson(defaultDateTimeFromJson);
    $JSON.addDefaultToJson(defaultDateTimeToJson);
    final now = DateTime.now();
    final a = BuildInJsonConverterBean()
      ..dateTime = now
      ..dateTime2 = [now];
    final json = jsonEncode(a.toJson());
    final map = jsonDecode(json);
    map['dateTime'] = defaultDateTimeToJson(now);
    map['dateTime2'] = [defaultDateTimeToJson(now)];
    final b = BuildInJsonConverterBean.fromJson(map);
    expect(jsonEncode(b.toJson()), json);
  });

  test('global_custom_default_date_converter_PARSE_ERROR', () async {
    $JSON.addDefaultFromJson(defaultDateTimeFromJson);
    $JSON.addDefaultToJson(defaultDateTimeToJson);
    final now = DateTime.now();
    final a = BuildInJsonConverterBean()
      ..dateTime = now
      ..dateTime2 = [now];
    final json = jsonEncode(a.toJson());
    final map = jsonDecode(json);
    map['dateTime'] = 'now.millisecondsSinceEpoch';
    map['dateTime2'] = [now.millisecondsSinceEpoch];
    try {
      BuildInJsonConverterBean.fromJson(map);
    } catch (e) {
      expect(e.toString().contains('FormatException'), true);
    }
  });

  test('auto_default_value', () async {
    $DefaultJSONConvert.nullDefaultFromJson = $nullDefaultFromJson;

    final preBean = AutoDefaultValueBean();
    expect(preBean.a0, 1);
    expect(preBean.a1, null);
    expect(preBean.list0, null);
    try {
      print(preBean.a);
    } catch (e) {
      expect(e.toString().contains('LateInitializationError'), true);
    }
    try {
      print(preBean.b);
    } catch (e) {
      expect(e.toString().contains('LateInitializationError'), true);
    }
    try {
      print(preBean.d);
    } catch (e) {
      expect(e.toString().contains('LateInitializationError'), true);
    }
    try {
      print(preBean.listInt);
    } catch (e) {
      expect(e.toString().contains('LateInitializationError'), true);
    }
    try {
      print(preBean.listBool);
    } catch (e) {
      expect(e.toString().contains('LateInitializationError'), true);
    }
    try {
      print(preBean.listDouble);
    } catch (e) {
      expect(e.toString().contains('LateInitializationError'), true);
    }
    try {
      print(preBean.listInt2);
    } catch (e) {
      expect(e.toString().contains('LateInitializationError'), true);
    }
    try {
      print(preBean.listBool2);
    } catch (e) {
      expect(e.toString().contains('LateInitializationError'), true);
    }
    try {
      print(preBean.listDouble2);
    } catch (e) {
      expect(e.toString().contains('LateInitializationError'), true);
    }

    final bean = AutoDefaultValueBean.fromJson(<String, dynamic>{});
    expect(bean.a0, 1); //keep field default value
    expect(bean.a1, 0);
    expect(bean.a, 0);
    expect(bean.b, false);
    expect(bean.d, 0.0);

    expect(bean.list0?.isEmpty, true);
    expect(bean.listInt.isEmpty, true);
    expect(bean.listBool.isEmpty, true);
    expect(bean.listDouble.isEmpty, true);

    expect(bean.listInt2.isEmpty, true);
    expect(bean.listBool2.isEmpty, true);
    expect(bean.listDouble2.isEmpty, true);
    expect(bean.map?.isEmpty, true);
    expect(bean.smartBoolList2?.isEmpty, true);
    expect(bean.smartBoolList, null); //not support auto default value, need define field with default value in code;
  });
}
